﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Daphne.GameShared;
using System.Diagnostics;

namespace GameEngine.PDNParser
{
    /// <summary>
    /// The main class to interpret the PDN raw model to GameManager model
    /// </summary>
    public class PDNInterpreter
    {
        /// <summary>
        /// Supported checkers types
        /// </summary>
        private Dictionary<int, enumGameType> _dicSupportedCheckers = new Dictionary<int, enumGameType>()
        {
            { 20, enumGameType.International },
            { 21, enumGameType.English },
            { 29, enumGameType.Czech }            
        };

        private FENParser _fenParser = null;


        public PDNInterpreter()
        {
        }


        #region Single Match Line Intepretation - aimed to be removes later       

        /// <summary>
        /// Interpreting with resolving the checkers type
        /// </summary>
        public GameManager InterpretGame(PDNRawGame game)
        {
            // resolve game type
            enumGameType? type = ResolveGameType(game);

            //if (type.HasValue == false)
            //    return null;

            // DEBUG - default try czech checkers
            if (type.HasValue == false)
                type = enumGameType.Czech;

            // create game manager
            GameManager mng = GameManagerFactory.CreateGameManager(type.Value);

            // interpret game
            InterpretGame(game, mng);

            return mng;          
        }

        /// <summary>
        /// Resolve the game type
        /// </summary>
        private enumGameType? ResolveGameType(PDNRawGame game)
        {
            int iGameType;
            foreach (var header in game.Headers)
            {
                if (header.Key == PDNHeaderKey.GameType)
                {
                    int.TryParse(header.Value, out iGameType);
                    enumGameType eGameType;
                    if (_dicSupportedCheckers.TryGetValue(iGameType, out eGameType) == false)
                        return null;

                    return eGameType;                   
                }
            }

            return null;
        }

        /// <summary>
        /// Interpret the game itself
        /// </summary>
        /// <param name="rawGame"></param>
        /// <param name="gm"></param>
        public void InterpretGame(PDNRawGame rawGame, GameManager gm)
        {
            // init helper classes
            _fenParser = new FENParser(gm.GameType);       

            // check the starting position
            CheckStartingPosition(gm, rawGame);

            // create PDN Headers
            foreach (var rawPDNHeaer in rawGame.Headers)
            {
                gm.PDNHeaders[rawPDNHeaer.Key] = rawPDNHeaer.Value;
            }

            // convert moves
            foreach (PDNRawGameBodyItem rawItem in rawGame.GameBody.Items)
            {
                // convert moves
                Position startPos = Position.Empty;
                Position endPos = Position.Empty;
                Move move = null;


                if (rawItem is PDNRawMove)
                {
                    PDNRawMove rawMove = rawItem as PDNRawMove;

                    if (rawMove.Positions.Count > 0)
                    {
                        string sPosition = rawMove.Positions[0].Position;
                        if (gm.TryParsePosition(sPosition, out startPos) == false)
                        {
                            //throw new ApplicationException(string.Format("Error position: {0}", sPosition));
                        }


                        // iterate through position
                        for (int moveIndex = 1; moveIndex < rawMove.Positions.Count; moveIndex++)
                        {
                            sPosition = rawMove.Positions[moveIndex].Position;
                            /*if (*/ gm.TryParsePosition(sPosition, out endPos); // == false)
                                // new ApplicationException(string.Format("Error position: {0}", sPosition));

                            // check the move
                            if (gm.CanFigureMoveHere(startPos, endPos, move == null ? false : true) == false)
                            {
                                // new ApplicationException("Illegal move");
                            }

                            Move moveStep = gm.CompleteMoveStep(startPos, endPos);
                            gm.DoMoveStep(moveStep);
                            if (move == null)
                                move = moveStep;
                            else
                            {
                                Move lastMove = move;
                                while (lastMove.nextMove != null)
                                    lastMove = lastMove.nextMove;
                                lastMove.nextMove = moveStep;
                            }

                            startPos = endPos;
                            endPos = Position.Empty;

                        } // for

                        if (move != null)
                            gm.FinalizeMove(move);

                        // save move stregth
                        move.MoveStrength = rawMove.MoveStrength;
                    }
                }
            }
        }

        #endregion

        #region Complete Interpretation - Model Conversion

        public GameManager MakeCompleteInterpretation(PDNRawGame game, bool diagnostics = true)
        {
            // init helper classes
            _fenParser = new FENParser(game.GameType);

            // check the game type - default value Czech Checkers
            // TO-DO: probing of the game type
            enumGameType? gameType = ResolveGameType(game);

            GameManager gm = null;
            StringBuilder sb = new StringBuilder();
            
            // if checkers type defined
            if(gameType.HasValue)
            {
                gm = GameManagerFactory.CreateGameManager(gameType.Value);

                // check the starting position
                CheckStartingPosition(gm, game);

                GameTree root = ConvertGame(game, gm);
            }
            else if(diagnostics)
            {

                // else try to diagnose the type of checkers
                foreach(var pair in _dicSupportedCheckers)
                {
                    try
                    {
                        gm = GameManagerFactory.CreateGameManager(pair.Value);

                        // check the starting position
                        CheckStartingPosition(gm, game);

                        GameTree root = ConvertGame(game, gm);
                        break;
                    }
                    catch(Exception ex)
                    {                       
                        gm = null;
                        sb.AppendLine(string.Format("{0} - {1}", pair.Value.ToString(), ex.Message));
                        sb.AppendLine(ex.StackTrace);
                        continue;
                    }

                }
            }

            if (gm == null)
                throw new Exception("Unsupported type of draughts.\n" + sb.ToString());

            // DEBUG TO-DO:
            // assign the root to the GameManager
            
            return gm;
        }

        /// <summary>
        /// Checks the starting position - default or special
        /// </summary>
        private void CheckStartingPosition(GameManager gm, PDNRawGame game)
        {
            gm.NewGame();

            foreach (var header in game.Headers)
            {
                // FEN Setup found
                if (header.Key == PDNHeaderKey.FENSetup)
                {
                    string sFENValue = header.Value;

                    // parse it
                    BitBoard bb = _fenParser.Parse(sFENValue);
                    
                    // upload it to the gameboard
                    gm.GameBoard.UnpackGameBoard(bb);
                }
            }
        }

        /// <summary>
        /// Convert the game
        /// </summary>
        private GameTree ConvertGame(PDNRawGame rawGame, GameManager gm)
        {
            // create PDN Headers
            foreach (var rawPDNHeaer in rawGame.Headers)
            {
                gm.PDNHeaders[rawPDNHeaer.Key] = rawPDNHeaer.Value;
            }

            // convert the game itself
            GameTree root = new GameTree(null);
            ConvertGameBody(rawGame.GameBody, root, gm);
            return root;
        }

        /// <summary>
        /// switches among methods for converting specific game items
        /// </summary>
        private GameTree ConvertGameItem(PDNRawGameBodyItem item, GameManager gm)
        {
            GameTree treeItem = null;
            GameItem gameItem = null;

            if (item is PDNRawComment)
            {
                gameItem = ConvertComment(item as PDNRawComment);
            }
            else if (item is PDNRawMove)
            {
                gameItem = ConvertMove(item as PDNRawMove, gm);
            }
            else if (item is PDNRawNAG)
            {
                gameItem = ConvertNAG(item as PDNRawNAG);
            }
            else if (item is PDNRawGameBody)
            {
                gameItem = new GameVariation();
                treeItem = new GameTree(gameItem);

                // save the state of game manager before parsing the variation
                int moveIndex = gm.MoveHistory.GetMoveIndex();
                // convert the variation
                ConvertGameBody(item as PDNRawGameBody, treeItem, gm);
                // restore the previous state of move in the game match
                gm.MoveHistory.MoveToIndex(moveIndex);
            }
            else if (item is PDNRawFENSetupString)
            {
                gameItem = ConvertFENSetup(item as PDNRawFENSetupString);
            }
            else
                throw new Exception("Unknown PDN Raw GameItem");

            if (treeItem == null)
            {
                treeItem = new GameTree(gameItem);
            }
            return treeItem;           
        }


        /// <summary>
        /// Converts the game body - single linea of moves and other complementary items
        /// </summary>
        private void ConvertGameBody(PDNRawGameBody gameBody, GameTree parent, GameManager gm)
        {
            // iterate among all child items
            foreach (var item in gameBody.Items)
            {
                GameTree treeItem = ConvertGameItem(item, gm);
                parent.AddChild(treeItem);
            }
        }

        /// <summary>
        /// Convert the move
        /// </summary>
        private GameItem ConvertMove(PDNRawMove rawMove, GameManager gm)
        {
            if (rawMove.EmptyMove)
                return new EmptyMove();
            else
            {
                // init variables
                Position startPos = Position.Empty;
                Position endPos = Position.Empty;
                Move move = null;

                Debug.Assert(rawMove.Positions.Count != 0);

                // check the starting position
                string sPosition = rawMove.Positions[0].Position;
                if (gm.TryParsePosition(sPosition, out startPos) == false)
                {
                    throw new Exception(string.Format("Error position: {0}", sPosition));
                }

                bool investigationDone = false;

                // iterate through position
                for (int moveIndex = 1; moveIndex < rawMove.Positions.Count; moveIndex++)
                {
                    // check validity of position
                    sPosition = rawMove.Positions[moveIndex].Position;
                    if (gm.TryParsePosition(sPosition, out endPos) == false)
                        new Exception(string.Format("Error position: {0}", sPosition));

                    // check whether the move is legal
                    if (gm.CanFigureMoveHere(startPos, endPos, move == null ? false : true) == false)
                    {
                        //// check, if there is recorded just original and target field
                        //if(moveIndex == 1 && rawMove.Positions.Count == 2)
                        {
                            // check whether we have a shorted record of the move
                            // invoke the move investigation
                            List<Move> moves = gm.InvestigateMove(startPos, endPos);

                            // check, if we have only one move
                            if (moves.Count == 1)
                            {
                                Move investMove = moves[0];

                                // do move      
                                gm.DoMoveStep(investMove);

                                // attach the move
                                if (move == null)
                                {
                                    move = investMove;
                                }
                                else
                                {
                                    Move lastMove = move;
                                    while (lastMove.nextMove != null)
                                        lastMove = lastMove.nextMove;
                                    lastMove.nextMove = investMove;
                                }

                                startPos = endPos;
                                endPos = Position.Empty;
                                continue;
                            }
                            else
                            {
                                throw new Exception("Illegal move");
                            }
                        }

                        
                    }

                    // complete the move and attach to the move-step chain
                    Move moveStep = gm.CompleteMoveStep(startPos, endPos);
                    gm.DoMoveStep(moveStep);
                    if (move == null)
                        move = moveStep;
                    else
                    {
                        Move lastMove = move;
                        while (lastMove.nextMove != null)
                            lastMove = lastMove.nextMove;
                        lastMove.nextMove = moveStep;
                    }

                    startPos = endPos;
                    endPos = Position.Empty;

                } // for

                //if(!investigationDone)
                gm.FinalizeMove(move);

                // save move stregth
                move.MoveStrength = rawMove.MoveStrength;

                return move;
            }
        }
        
        /// <summary>
        /// Converts FEN Position Setup 
        /// </summary>
        private GameItem ConvertFENSetup(PDNRawFENSetupString fen)
        {
            BitBoard bb;
            bb = _fenParser.Parse(fen.FENSetup);
            return new GamePosition(bb);
        }

        /// <summary>
        /// Converts the PDN Comment
        /// </summary>
        private GameItem ConvertComment(PDNRawComment comment)
        {
            return new GameComment(comment.Comment);
        }

        /// <summary>
        /// Converts the NAG glyph
        /// </summary>
        private GameItem ConvertNAG(PDNRawNAG nag)
        {
            int nagValue;
            if(int.TryParse(nag.NagValue, out nagValue) == false)
                throw new Exception("Unknown NAG Value");

            return new GameNAG(nagValue);
        }

        #endregion



    }
}
